package com.google.code.opengw.server.support;

import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.google.code.opengw.policy.Policy;
import com.google.code.opengw.policy.PolicyFactory;
import com.google.code.opengw.policy.PolicyParam;
import com.google.code.opengw.policy.PolicyType;
import com.google.code.opengw.server.Constants;
import com.google.code.opengw.server.Server;
import com.google.code.opengw.server.deployment.Deployment;
import com.google.code.opengw.server.deployment.FileHolder;
import com.google.code.opengw.server.domain.Domain;
import com.google.code.opengw.server.domain.Publish;
import com.google.code.opengw.server.domain.Route;
import com.google.code.opengw.server.domain.RouteType;
import com.google.code.opengw.support.OpenGWException;

public class XMLParser {

	public static Publish parsePublish(Domain domain, Deployment deployment) throws OpenGWException, SAXException, IOException, ParserConfigurationException {

		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
		Document doc = docBuilder.parse(deployment.getPublishXMLStream());
		doc.getDocumentElement().normalize();

		String publishName = deployment.getPublishName();
		
		Node domainNameNode = XMLParser.getFirstNodeFromDoc(doc,
				Constants.PUBLISH_FILE_DOMAIN_NAME,
				Server.PUBLISH_FILE_NAME, true);
		String domainName = domainNameNode.getFirstChild().getNodeValue();
		
		if(domain != null && !domain.getName().equals(domainName))
			throw new OpenGWException(OpenGWException.DOMAIN_NAME_IS_INCORRECT, publishName, domainName);

		NodeList routeNodeList = XMLParser.searchElementsFromDoc(
				doc,
				Constants.PUBLISH_FILE_ROUTE,
				new OpenGWException(OpenGWException.ROUTE_NOT_FOUND_IN_PUBLISH,
								publishName));

		Route[] routeList = new Route[routeNodeList.getLength()];
		for (int i = 0; i < routeNodeList.getLength(); i++) {
			Node routeNode = routeNodeList.item(i);
			Route route = parseRoute((Element)routeNode, deployment);
			ConfigurationUtils.checkRouteList(route, routeList);
			routeList[i] = route;
		}
		
		return new Publish(publishName, domainName, routeList);
		
	}

	public static Route parseRoute(Element routeNode,Deployment deployment)
			throws IOException, OpenGWException {
		Server server = Server.getInstance();
		Node routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_ROUTE_NAME, Server.PUBLISH_FILE_NAME,
				true);
		String routeName = routeSubElementNode.getFirstChild().getNodeValue();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_REQUEST_PATH,
				Server.PUBLISH_FILE_NAME, true);
		String requestPath = routeSubElementNode.getFirstChild().getNodeValue();
		
		boolean pathExtensionAvaliable = false;
		if(requestPath.endsWith("**")){
			pathExtensionAvaliable = true;
			requestPath = requestPath.substring(0, requestPath.length() - 2);
		}

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_ROUTE_URL, Server.PUBLISH_FILE_NAME,
				true);
		String routeURL = routeSubElementNode.getFirstChild().getNodeValue();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_AVAILABLE_METHODS,
				Server.PUBLISH_FILE_NAME, false);
		String availableMethods = null;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes())
			availableMethods = routeSubElementNode.getFirstChild()
					.getNodeValue();
		if (availableMethods == null || availableMethods.equals(""))
			availableMethods = "" + server.getAvailableMethods();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_ALLOW_PARAMETERS,
				Server.PUBLISH_FILE_NAME, false);
		boolean allowParameters = false;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes()) {
			allowParameters = routeSubElementNode.getFirstChild()
					.getNodeValue().equalsIgnoreCase("YES") ? true : false;
		} else {
			allowParameters = server.isAllowParameters();
		}

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_CONNECTION_TIMEOUT,
				Server.PUBLISH_FILE_NAME, false);
		String connectionTimeout = null;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes())
			connectionTimeout = routeSubElementNode.getFirstChild()
					.getNodeValue();
		if (connectionTimeout == null || connectionTimeout.equals(""))
			connectionTimeout = ""
					+ server.getConnectionTimeout();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_READ_TIMEOUT,
				Server.PUBLISH_FILE_NAME, false);
		String readTimeout = null;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes())
			readTimeout = routeSubElementNode.getFirstChild().getNodeValue();
		if (readTimeout == null || readTimeout.equals(""))
			readTimeout = "" + server.getReadTimeout();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_CONTENT_TYPE,
				Server.PUBLISH_FILE_NAME, false);
		String contentType = null;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes())
			contentType = routeSubElementNode.getFirstChild().getNodeValue();

		routeSubElementNode = XMLParser.getFirstNodeFromElement(routeNode,
				Constants.PUBLISH_FILE_RESOURCE_URL,
				Server.PUBLISH_FILE_NAME, false);
		String resourceURL = null;
		if (routeSubElementNode != null && routeSubElementNode.hasChildNodes())
			resourceURL = routeSubElementNode.getFirstChild().getNodeValue();

		NodeList routePolicyNodeList = routeNode
				.getElementsByTagName(Constants.PUBLISH_FILE_POLICY);

		Policy[] policyList = new Policy[routePolicyNodeList.getLength()];
		for (int policyIndex = 0; policyIndex < routePolicyNodeList.getLength(); policyIndex++) {
			policyList[policyIndex] = parsePolicy((Element) routePolicyNodeList
					.item(policyIndex));
		}

		String[] methodsArr = availableMethods.split(",");
		for (String method : methodsArr) {
			if (!Constants.HTTP_SUPPORTED_METHODS
					.contains(method.toUpperCase()))
				throw new OpenGWException(OpenGWException.UNSUPPORTED_HTTP_METHOD, method);
		}

		if (!ConfigurationUtils.checkRouteURL(routeURL))
			throw new OpenGWException(OpenGWException.INVALID_ROUTE_URL, routeName, routeURL);
		if (!ConfigurationUtils.checkRequestPath(requestPath))
			throw new OpenGWException(OpenGWException.INVALID_REQUEST_PATH, requestPath);
		
		
		RouteType routeType = null;
		
		if(routeURL.equalsIgnoreCase("none")){
			if(resourceURL == null || resourceURL.equals(""))
				throw new OpenGWException(OpenGWException.ROUTE_NEEDED_FOR_PUBLISH, routeName);
			else if(deployment.checkResource(resourceURL))
				throw new OpenGWException(OpenGWException.ROUTE_NEEDED_FOR_PUBLISH, routeName);
			routeType = RouteType.RESOURCE;
		} else {
			int customOperationCount = 0;
			for (Policy policy : policyList) {
				if (routeType == null 
						&& policy.getPolicyType() == PolicyType.CUSTOM_OPERATION) {
					routeType = RouteType.CUSTOM;
					customOperationCount++;
				} else if(routeType == RouteType.CUSTOM
						&& customOperationCount == 1)
					throw new OpenGWException(OpenGWException.ONLY_ONE_CUSTOM_OPERATION_POLICY,
									routeName);
			}
			if(routeType == null)
				routeType = RouteType.ROUTE;
		}

		return new Route(deployment.getPublishName(), routeName, requestPath, routeURL, resourceURL,
				contentType, availableMethods, pathExtensionAvaliable, allowParameters,
				Integer.parseInt(connectionTimeout),
				Integer.parseInt(readTimeout), routeType, policyList);

	}

	public static Policy parsePolicy(Element policyElement)
			throws OpenGWException {
		Node policySubElementNode = XMLParser.getFirstNodeFromElement(
				policyElement, Constants.PUBLISH_FILE_POLICY_NAME,
				Server.PUBLISH_FILE_NAME, true);
		String policyName = policySubElementNode.getFirstChild().getNodeValue();

		policySubElementNode = XMLParser.getFirstNodeFromElement(policyElement,
				Constants.PUBLISH_FILE_POLICY_CLASS,
				Server.PUBLISH_FILE_NAME, true);
		String policyClass = policySubElementNode.getFirstChild()
				.getNodeValue();

		policySubElementNode = XMLParser.getFirstNodeFromElement(policyElement,
				Constants.PUBLISH_FILE_POLICY_TYPE,
				Server.PUBLISH_FILE_NAME, true);
		String policyType = policySubElementNode.getFirstChild().getNodeValue();

		NodeList policyParamElementNodeList = policyElement
				.getElementsByTagName(Constants.PUBLISH_FILE_POLICY_PARAM);
		PolicyParam[] policyParamList = new PolicyParam[policyParamElementNodeList
				.getLength()];
		for (int policyParamIndex = 0; policyParamIndex < policyParamElementNodeList
				.getLength(); policyParamIndex++) {
			Node policyParamElementNode = policyParamElementNodeList
					.item(policyParamIndex);

			Node policyParamSubElementNode = XMLParser.getFirstNodeFromElement(
					((Element) policyParamElementNode),
					Constants.PUBLISH_FILE_POLICY_PARAM_NAME,
					Server.PUBLISH_FILE_NAME, true);
			String paramName = policyParamSubElementNode.getFirstChild()
					.getNodeValue();

			policyParamSubElementNode = XMLParser.getFirstNodeFromElement(
					((Element) policyParamElementNode),
					Constants.PUBLISH_FILE_POLICY_PARAM_VALUE,
					Server.PUBLISH_FILE_NAME, true);
			String paramValue = policyParamSubElementNode.getFirstChild()
					.getNodeValue();

			policyParamList[policyParamIndex] = new PolicyParam(paramName,
					paramValue);

		}
		try {
			PolicyFactory policyFactory = PolicyFactory.getInstance();
			Policy policy = policyFactory.createPolicy(policyClass);
			policy.init(policyName, policyType, policyParamList);
			return policy;
		} catch (OpenGWException e) {
			throw e;
		} catch (Exception e) {
			throw new OpenGWException(OpenGWException.INVALID_POLICY_CLASS, e, policyClass);
		}
	}

	public static NodeList searchElementsFromDoc(Document doc, String tagName,
			String fileName) throws OpenGWException {
		NodeList nodeList = doc.getElementsByTagName(tagName);
		if (nodeList == null || nodeList.getLength() == 0)
			throw new OpenGWException(OpenGWException.ELEMENT_NOT_FOUND, tagName, fileName);

		return nodeList;
	}

	public static NodeList searchElementsFromDoc(Document doc, String tagName,
			OpenGWException ex) throws OpenGWException {
		NodeList nodeList = doc.getElementsByTagName(tagName);
		if (nodeList == null || nodeList.getLength() == 0)
			throw ex;

		return nodeList;
	}

	public static Node getFirstNodeFromDoc(Document doc, String tagName,
			String fileName, boolean required) throws OpenGWException {
		NodeList nodeList = doc.getElementsByTagName(tagName);
		if (required && (nodeList == null || nodeList.getLength() == 0))
			throw new OpenGWException(OpenGWException.ELEMENT_NOT_FOUND, tagName, fileName);
		Node node = nodeList.item(0);
		if (required && !node.hasChildNodes())
			throw new OpenGWException(OpenGWException.VALUE_NOT_FOUND, tagName, fileName);
		return node;
	}

	public static Node getFirstNodeFromElement(Element element, String tagName,
			String fileName, boolean required) throws OpenGWException {
		NodeList nodeList = element.getElementsByTagName(tagName);
		if (required && (nodeList == null || nodeList.getLength() == 0))
			throw new OpenGWException(OpenGWException.ELEMENT_NOT_FOUND, tagName, fileName);
		else if (!required && (nodeList == null || nodeList.getLength() == 0))
			return null;
		Node node = nodeList.item(0);
		if (required && !node.hasChildNodes())
			throw new OpenGWException(OpenGWException.VALUE_NOT_FOUND, tagName, fileName);
		return node;
	}
}
